/**
 * GBK to UTF-8 Filename Converter
 * 
 * A tool to convert filenames from GBK encoding to UTF-8 encoding
 * with optional recursive directory processing.
 */

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <iconv.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

/* Constants */
#define BUFFER_SIZE 1024
#define MAX_PATH_LENGTH 4096
#define DEFAULT_RECURSIVE_MODE 0

/* Error codes */
typedef enum {
    ERROR_NONE = 0,
    ERROR_INVALID_ARGUMENT,
    ERROR_ICONV_INIT_FAILED,
    ERROR_DIRECTORY_ACCESS,
} ErrorCode;

/* Conversion context */
typedef struct {
    iconv_t converter;
    unsigned int converted_files;
    unsigned int skipped_files;
    unsigned int error_count;
    int recursive_mode;
} ConversionContext;

/* Forward declarations */
static int validate_utf8(const char *str);
static int initialize_conversion_context(ConversionContext *ctx);
static void cleanup_conversion_context(ConversionContext *ctx);
static int convert_filename(ConversionContext *ctx, const char *input, 
                          char *output, size_t output_size);
static void process_directory_entry(const char *dir_path, 
                                   const char *entry_name,
                                   ConversionContext *ctx);
static void process_directory(const char *path, ConversionContext *ctx);

/**
 * Validate UTF-8 encoding
 * 
 * @param str Input string to validate
 * @return 1 if valid UTF-8, 0 otherwise
 */
static int validate_utf8(const char *str)
{
    while (*str) {
        unsigned char c = *str++;
        
        // Single-byte character (0xxxxxxx)
        if (c <= 0x7F) continue;

        // Multi-byte sequences
        size_t remaining_bytes = 0;
        if ((c & 0xE0) == 0xC0) remaining_bytes = 1;   // 2-byte
        else if ((c & 0xF0) == 0xE0) remaining_bytes = 2; // 3-byte
        else if ((c & 0xF8) == 0xF0) remaining_bytes = 3; // 4-byte
        else return 0; // Invalid leading byte

        for (size_t i = 0; i < remaining_bytes; i++) {
            if ((*str++ & 0xC0) != 0x80)
                return 0;
        }
    }
    return 1;
}

/**
 * Initialize conversion context
 * 
 * @param ctx Context to initialize
 * @return 0 on success, non-zero on error
 */
static int initialize_conversion_context(ConversionContext *ctx)
{
    ctx->converter = iconv_open("UTF-8", "GBK");
    if (ctx->converter == (iconv_t)-1) {
        perror("[ERROR] Failed to initialize iconv");
        return ERROR_ICONV_INIT_FAILED;
    }

    ctx->converted_files = 0;
    ctx->skipped_files = 0;
    ctx->error_count = 0;
    ctx->recursive_mode = DEFAULT_RECURSIVE_MODE;
    return ERROR_NONE;
}

/**
 * Cleanup conversion resources
 */
static void cleanup_conversion_context(ConversionContext *ctx)
{
    if (ctx->converter != (iconv_t)-1) {
        iconv_close(ctx->converter);
    }
}

/**
 * Convert filename encoding
 * 
 * @param ctx Conversion context
 * @param input Source filename
 * @param output Output buffer
 * @param output_size Size of output buffer
 * @return 0 on success, errno code on failure
 */
static int convert_filename(ConversionContext *ctx, const char *input,
                           char *output, size_t output_size)
{
    // Preserve original name for valid UTF-8
    strncpy(output, input, output_size - 1);
    output[output_size - 1] = '\0';

    if (validate_utf8(input)) {
        return ERROR_NONE;
    }

    char *input_ptr = (char *)input;
    char *output_ptr = output;
    size_t input_len = strlen(input);
    size_t output_len = output_size - 1;

    iconv(ctx->converter, NULL, NULL, NULL, NULL); // Reset state

    if (iconv(ctx->converter, &input_ptr, &input_len, 
             &output_ptr, &output_len) == (size_t)-1) {
        return errno;
    }

    *output_ptr = '\0';
    return ERROR_NONE;
}

/**
 * Process single directory entry
 */
static void process_directory_entry(const char *dir_path,
                                   const char *entry_name,
                                   ConversionContext *ctx)
{
    char old_path[MAX_PATH_LENGTH];
    char new_path[MAX_PATH_LENGTH];
    char converted_name[BUFFER_SIZE];
    struct stat file_info;

    // Construct full path
    if (snprintf(old_path, MAX_PATH_LENGTH, "%s/%s", 
                dir_path, entry_name) >= MAX_PATH_LENGTH) {
        fprintf(stderr, "[WARNING] Path too long: %s/%s\n", 
               dir_path, entry_name);
        ctx->error_count++;
        return;
    }

    // Get file metadata
    if (lstat(old_path, &file_info) != 0) {
        fprintf(stderr, "[ERROR] Cannot access %s: %s\n", 
               old_path, strerror(errno));
        ctx->error_count++;
        return;
    }

    // Process directory recursively first
    if (S_ISDIR(file_info.st_mode) && ctx->recursive_mode) {
        process_directory(old_path, ctx);
    }

    // Convert filename
    int convert_result = convert_filename(ctx, entry_name, 
                                         converted_name, sizeof(converted_name));
    if (convert_result != ERROR_NONE) {
        fprintf(stderr, "[ERROR] Conversion failed for %s: Code %d\n", 
               old_path, convert_result);
        ctx->error_count++;
        return;
    }

    // Skip unchanged names
    if (strcmp(entry_name, converted_name) == 0) {
        ctx->skipped_files++;
        return;
    }

    // Build new path
    if (snprintf(new_path, MAX_PATH_LENGTH, "%s/%s", 
                dir_path, converted_name) >= MAX_PATH_LENGTH) {
        fprintf(stderr, "[WARNING] New path too long: %s\n", converted_name);
        ctx->error_count++;
        return;
    }

    // Perform rename operation
    if (rename(old_path, new_path) != 0) {
        fprintf(stderr, "[ERROR] Rename failed %s -> %s: %s\n", 
               old_path, new_path, strerror(errno));
        ctx->error_count++;
        return;
    }

    printf("[OK] Renamed: %s -> %s\n", old_path, new_path);
    ctx->converted_files++;
}

/**
 * Process directory contents
 */
static void process_directory(const char *path, ConversionContext *ctx)
{
    DIR *dir_stream = opendir(path);
    if (dir_stream == NULL) {
        fprintf(stderr, "[ERROR] Cannot open directory %s: %s\n", 
               path, strerror(errno));
        ctx->error_count++;
        return;
    }

    struct dirent *entry;
    while ((entry = readdir(dir_stream)) != NULL) {
        // Skip special entries
        if (strcmp(entry->d_name, ".") == 0 || 
            strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        process_directory_entry(path, entry->d_name, ctx);
    }

    closedir(dir_stream);
}

/**
 * Display help message
 */
static void display_help(const char *program_name)
{
    printf("Filename Encoding Converter - GBK to UTF-8\n\n"
           "Usage: %s [OPTIONS] DIRECTORY\n\n"
           "Options:\n"
           "  -r, --recursive   Process directories recursively\n"
           "  -h, --help        Display this help message\n",
           program_name);
}

int main(int argc, char *argv[])
{
    ConversionContext ctx;
    int opt;

    static struct option long_options[] = {
        {"recursive", no_argument, NULL, 'r'},
        {"help",      no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };

    // Initialize context
    if (initialize_conversion_context(&ctx) != ERROR_NONE) {
        return EXIT_FAILURE;
    }

    // Parse command line options
    while ((opt = getopt_long(argc, argv, "rh", long_options, NULL)) != -1) {
        switch (opt) {
        case 'r':
            ctx.recursive_mode = 1;
            break;
        case 'h':
            display_help(argv[0]);
            cleanup_conversion_context(&ctx);
            return EXIT_SUCCESS;
        default:
            display_help(argv[0]);
            cleanup_conversion_context(&ctx);
            return EXIT_FAILURE;
        }
    }

    if (optind >= argc) {
        fprintf(stderr, "[ERROR] Target directory not specified\n");
        display_help(argv[0]);
        cleanup_conversion_context(&ctx);
        return EXIT_FAILURE;
    }

    const char *target_dir = argv[optind];
    process_directory(target_dir, &ctx);

    // Print summary
    printf("\nConversion Summary:\n"
           "--------------------------------\n"
           "Operating Mode:      %s\n"
           "Converted Files:     %u\n"
           "Skipped Files:       %u\n"
           "Errors Encountered:  %u\n",
           ctx.recursive_mode ? "Recursive" : "Single Directory",
           ctx.converted_files,
           ctx.skipped_files,
           ctx.error_count);

    cleanup_conversion_context(&ctx);
    return ctx.error_count ? EXIT_FAILURE : EXIT_SUCCESS;
}
